package bancosys.tec.datadigester.registry;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import jmine.tec.datadigester.DataDigesterMessages;

import org.apache.log4j.Logger;

import bancosys.tec.datadigester.DigesterBean;
import bancosys.tec.datadigester.DigesterDriver;
import bancosys.tec.utils.reflection.ReflectionUtils;

/**
 * Essa classe deveria ser singleton! O Spring vai nos garantir isso. Caso a classe não seja injetada, tomar cuidado para usar uma única
 * instância.
 * 
 * @author marco
 */
public class DriverRegistry {

    private static Logger log = Logger.getLogger(DriverRegistry.class);

    /**
     * Mapa com o par &lt; Código do arquivo, Classe do driver &gt;.
     */
    private Map<String, Class<? extends DigesterDriver<? extends DigesterBean>>> registry =
            new HashMap<String, Class<? extends DigesterDriver<? extends DigesterBean>>>();

    /**
     * Tenta registrar um driver para um arquivo. Caso ocorra algum erro, loga o erro ocorrido.
     * 
     * @param fileCode Código do arquivo a ser registrado.
     * @param driverClassName Endereço da classe correspondente ao driver desse arquivo
     * @return se registrou o driver com sucesso.
     */
    @SuppressWarnings("unchecked")
    public boolean register(String fileCode, String driverClassName) {
        if (fileCode == null || fileCode.length() == 0) {
            this.log("Código do arquivo não pode ser null ou vazio.", fileCode, driverClassName);
            return false;
        }

        if (driverClassName == null || driverClassName.length() == 0) {
            this.log("Classe do driver não pode ser null ou vazio.", fileCode, driverClassName);
            return false;
        }

        if (this.registry.get(fileCode) != null) {
            this.log("Já existe um driver registrado para essa classe.", fileCode, driverClassName);
            return false;
        }

        Class<? extends DigesterDriver<? extends DigesterBean>> driverClass;
        try {
            driverClass =
                    (Class<? extends DigesterDriver<? extends DigesterBean>>) ReflectionUtils.findClass(driverClassName, this.getClass()
                            .getClassLoader());
            if (!DigesterDriver.class.isAssignableFrom(driverClass)) {
                this.log("Classe do driver não implementar DigesterDriver.", fileCode, driverClassName);
                return false;
            }
        } catch (ClassNotFoundException e) {
            this.log("Classe do driver não encontrada no classpath.", fileCode, driverClassName);
            return false;
        }

        if (DriverRegistry.log.isInfoEnabled()) {
            DriverRegistry.log.info("Registrando driver (fileCode: " + fileCode + ";driverClass: " + driverClass + ")");
        }
        this.registry.put(fileCode, driverClass);
        return true;
    }

    /**
     * Loga o erro ocorrido durante o registro de um driver.
     * 
     * @param cause causa do erro.
     * @param fileCode código do arquivo.
     * @param driverClass classe do driver.
     */
    private void log(String cause, String fileCode, String driverClass) {
        String message =
                new StringBuilder().append("Não foi possível registrar driver. ").append(cause).append(" Código do arquivo: ")
                        .append(fileCode).append(". Classe do driver: ").append(driverClass).toString();
        DriverRegistry.log.warn(message);
    }

    /**
     * @param fileCode Código do arquivo que deve ser recuperado
     * @return O driver correspondente a esse arquivo ou null caso o código não esteja registrado corretamente.
     */
    public DigesterDriver<? extends DigesterBean> retrieve(String fileCode) {
        Class<? extends DigesterDriver<? extends DigesterBean>> driverClass = this.registry.get(fileCode);
        if (driverClass == null) {
            throw new DriverRegistryException(DataDigesterMessages.NO_PROCESSOR_FOR_CODE.create(fileCode));
        }

        try {
            return driverClass.newInstance();
        } catch (InstantiationException e) {
            throw new DriverRegistryException(DataDigesterMessages.ERROR_CREATING_DRIVER.create(fileCode, driverClass), e);
        } catch (IllegalAccessException e) {
            throw new DriverRegistryException(DataDigesterMessages.ERROR_CREATING_DRIVER.create(fileCode, driverClass), e);
        }
    }

    /**
     * @return quantos drivers estão registrados.
     */
    public int size() {
        return this.registry.size();
    }

    /**
     * Verifica se o registro contém driver registrado para o arquivo informado.
     * 
     * @param fileCode código do arquivo.
     * @return <code>true</code> caso o arquivo possua um driver registrado.
     */
    public boolean contains(String fileCode) {
        return this.registry.containsKey(fileCode);
    }

    /**
     * Devolve um conjunto com todos os códigos de arquivos registrados.
     * 
     * @return um conjunto com todos os códigos de arquivos registrados.
     */
    public Set<String> getFileCodes() {
        return this.registry.keySet();
    }
}
